Sound Sampling on an Apple ][,][+,//e
by John MacLean
Copyright (c) 1990 Apple Users' Group, Sydney
Republished from Applecations, a publication of the Apple Users' Group, Sydney, Australia.

with technical assistance by Richard Bennett.

  With the recent introduction of the Apple IIGS there has been much interest in sound sampling and digitising. I was recently surprised by the quality of some digitised sound generated on a //e, so I uncovered my old ][+, borrowed a drive off my GS and went to work.
  The sound is sampled through the old cassette port so if
you have kept your cassette leads over the years they may finally get used (again). The sound is played back through the internal speaker, so all you need is a cassette player or a CD for better sound quality. With the routines I will present here, you can achieve about 30 seconds of digitised sound in a 48K machine. The routines are written in assembler, but you can type them and enjoy them without knowing how they work.
  The routines are surprisingly simple:
The cassette-in port (location $C060) changes sign (the high bit changes) whenever there is a change in the electrical signal coming from the external sound. If the speaker is clicked every time the external signal changes, the sound is reproduced. This leads to the first program:


LOOP1      LDA   $C060    ;Loop until the cassette-in goes
           BPL   LOOP1    ;negative.
           LDA   $C030    ;Click the speaker.
           BIT   $00      ;Waste 3 machine cycles.
LOOP2      LDA   $C060    ;Loop until the cassette-in goes
           BMI   LOOP2    ;positive.
           LDA   $C030    ;Click the speaker.
           JMP   LOOP1    ;Keep going


This short routine takes the signal straight from the cassette-in port and reproduces the sound on the internal speaker. Note the BIT $00 instruction to waste 3 machine cycles - all loops should be the same number of machine cycles for the best quality sound reproduction. This routine is useful for adjusting your volume (and equalising if you have the equipment) to produce the best quality sound.
  Now what is needed is some way of recording the signal from the cassette-in port so the sound can be replayed later. The next routine does that recording:


********************************
*                              *
* CASSETTE PORT TO MEMORY PRGM *
*                              *
* WRITTEN BY JOHN MACLEAN 1987 *
*                              *
********************************

         LST   OFF

********************************

         ORG   $8000

********************************

* ZERO PAGE LOCATIONS

BUFF     EQU   $06
ZPAGE    EQU   $08

********************************

* HARDWARE PAGE LOCATIONS

CASSPORT EQU   $C060

********************************

* SET UP THE BUFFER

START    LDA   #$00
         STA   BUFF
         LDA   #$08
         STA   BUFF+1
         LDY   #$00

* FILL BUFFER WITH ZEROS TO ALLOW FOR TIME OUTS

         LDA   #$00
ZLOOP    STA   (BUFF),Y
         INY
         BNE   ZLOOP
         INC   BUFF+1
         BPL   ZLOOP

* RESET THE BUFFER

         LDA   #$08
         STA   BUFF+1

* LOOP HERE UNTIL CASSPORT BECOMES POSITIVE

MLOOP0   PHA              ;3
         PLA              ;4
MLOOP1   LDX   #$01       ;2
MLOOP2   LDA   CASSPORT   ;4
         BPL   PSAVE      ;2+
         BIT   ZPAGE      ;3
         INX              ;2
         BEQ   MSKIP      ;2+
         JSR   MDELAY12   ;12
         JMP   MLOOP2     ;3
MSAVE    TXA              ;2
         STA   (BUFF),Y   ;5+
MSKIP    INY              ;2
         BNE   MLOOP0     ;2+
         INC   BUFF+1     ;5
         BPL   MLOOP1     ;2+
MDELAY12 RTS

* LOOP HERE UNTIL CASSPORT BECOMES NEGATIVE

PLOOP0   PHA              ;3
         PLA              ;4
PLOOP1   LDX   #$01       ;2
PLOOP2   LDA   CASSPORT   ;4
         BMI   MSAVE      ;2+
         BIT   ZPAGE      ;3
         INX              ;2
         BEQ   PSKIP      ;2+
         JSR   PDELAY12   ;12
         JMP   PLOOP2     ;3
PSAVE    TXA              ;2
         STA   (BUFF),Y   ;5+
PSKIP    INY              ;2
         BNE   PLOOP0     ;2+
         INC   BUFF+1     ;5
         BPL   PLOOP1     ;2+
PDELAY12 RTS


The time interval between each change in sign of the cassette-in port is recorded sequentially from $0800 to $8000 in main memory. This is done by incrementing the X register each time the port is tested and unchanged. When it finally changes the X register is buffered. The routine consists of two almost identical smaller routines. These routines could be combined into one general routine, at the cost of about 10 machine cycles. The faster the loops, the higher the sampling rate, and thus the sampled sound is of higher quality. The unusual coding ensures that the number of cycles between each sampling of the cassette-in port is the same regardless of the branches taken.
  The sound is now recorded (at 28 machine cycle intervals) and must be played back at exactly the same speed. This is achieved by decrementing the buffered values and clicking the speaker once they become zero. In this way, the intervals between speaker clicks will be the same as the intervals between changes of sign of the cassette-in port during recording. The following code plays back the sound as described. It uses unconventional techniques to get the loops down to 28 machine cycles each so the sound will be accurately reproduced.


********************************
*                              *
*  MEMORY TO SPEAKER PROGRAM.  *
*                              *
* WRITTEN BY JOHN MACLEAN 1987 *
*                              *
********************************

         LST   OFF

********************************

         ORG   $8200

********************************

* ZERO PAGE LOCATIONS

BUFF     EQU   $06
ZPAGE    EQU   $08

********************************

* HARDWARE PAGE LOCATIONS

SPEAKER  EQU   $C030

********************************

* SET UP THE BUFFER

START    LDA   #$00
         STA   BUFF
         LDA   #$08
         STA   BUFF+1
         LDY   #$00
         JMP   LOOP1

* READ THE NEXT BYTE FROM THE BUFFER

LOOP0    INC   BUFF+1     ;5
         BMI   DELAY12    ;2+ (ALL DONE ?)
LOOP1    LDA   (BUFF),Y   ;5+
         TAX              ;2
         DEX              ;2
         BEQ   SKIP1      ;2+

* CHECK FOR ZERO (TIME OUT) AND HANDLE SEPARATELY
* OTHERWISE DELAY 28 CYCLES TIMES THE VALUE IN THE X
* REGISTER

         CPX   #$FF       ;2  (WAS IT A ZERO BYTE ?)
         BNE   DELAYX     ;2+
         JSR   DELAY12    ;12
         JSR   DELAY12    ;12
         NOP              ;2

* DELAY ((254 * 2) + 1) CYCLES

         DEX              ;2
LOOP3    JSR   DELAY12    ;12
         BIT   ZPAGE      ;3
         BIT   ZPAGE      ;3
         BIT   ZPAGE      ;3
         NOP              ;2
         DEX              ;2
         BNE   LOOP3      ;2+

* GO AND INCREMENT THE BUFFER POINTER BUT DO NOT CLICK
* SPEAKER

         JMP   SKIP2      ;3

* USE UP THE REST OF ANOTHER 28 CYCLES TIMING IT SO WE CAN
* BRANCH TO THE REST OF THE LOOP COMPLETING 56 (2 * 28)
* CYCLES
* ON QUEUE. IF X WAS GREATER THAN 2 THEN DELAY 28 CYCLES FOR
* EACH REMAINING DECREMENT OF THE X REGISTER

DELAYX   JSR   DELAY12    ;12
         BIT   ZPAGE      ;3
         NOP              ;2
         NOP              ;2
         DEX              ;2
         BEQ   SKIP1      ;2+

* THIS ROUTINE WILL DELAY 28 CYCLES TIMES THE X REGISTER
* NOTE THE NOP MAKES UP FOR THE PREVIOUS BRANCH NOT BEING
* TAKEN (1 CYCLE) AND THE LAST BRANCH OF THE DELAY LOOP NOT
* BEING TAKEN THE LAST TIME THROUGH THE LOOP (1 CYCLE)

LOOP2    JSR   DELAY12   ;12
         BIT   ZPAGE     ;3
         BIT   ZPAGE     ;3
         BIT   ZPAGE     ;3
         NOP             ;2
         DEX             ;2
         BNE   LOOP2     ;2+
         NOP             ;2

SKIP1    LDA   SPEAKER   ;4
SKIP2    INY             ;2
         BEQ   LOOP0     ;2+
         BIT   ZPAGE     ;3
         NOP             ;2
         JMP   LOOP1     ;3
DELAY12  RTS


  Once you have recorded your favourite musics, try modifying the routines. Possible modifications are:
  - Slow down the recording program (add some NOP'S) and the sound will play back faster.
  - Slow down the play back program and the sound will play back slower.
  - Modify both the recording and playback routines to increase the recording time. Use the following buffers so the tests can be BEQ, BMI, and BPL at the end of the buffers:
Main memory : $0800 -> $7FFF,
              $BFFF -> $8000,
              $D000 -> $FFFF (on the language card),
Aux memory  : $0800 -> $7FFF,
              $BFFF -> $8000,
              $D000 -> $FFFF (alternate language card).
  - Write a routine to play the sound in reverse (or just reverse the recorded buffer).

  Happy sampling, and there's no need to feel left out if you don't own a GS.

THIS CONTENT COPYRIGHT © 2007, APPLE MACINTOSH USERS' GROUP, SYDNEY
Permission has been obtained to make this material available on the Internet.

Permission is hereby granted for non-profit user groups to republish this content.
PLEASE CREDIT THE AUTHOR AND THE SOURCE: Applecations, publication of the Apple Users' Group, Sydney, Australia

THIS PAGE COPYRIGHT © 2007, ANDREW ROUGHAN